﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Migrator.Packets;
using Migrator.Services.RouteOptimization.Packets;
using Migrator.Entities;
using Migrator.Core;

namespace Migrator.Services.RouteOptimization
{
    /// <summary>
    /// Service responsible for mantaining a binding cache used in
    /// route optimization
    /// </summary>
    public class RouteOptimizationService : IService
    {
        public RouteOptimizationService(Node client)
        {
            this.Client = client;
            this.Client.BeforePacketSent += new Node.BeforePacketSentEventHandler(Client_BeforePacketSent);
        }

        public void Update()
        {
            this.bindingCache.RemoveExpired(Simulator.Instance.Clock);
        }

        public bool Filter(IPPacket packet)
        {
            return packet is BindingUpdatePacket || packet is BindingWarningPacket;
        }

        public void Handle(IPPacket packet)
        {
            BindingUpdatePacket updatePacket = packet as BindingUpdatePacket;
            if (updatePacket != null)
                this.Handle(updatePacket);

            BindingWarningPacket warningPacket = packet as BindingWarningPacket;
            if (warningPacket != null)
                this.Handle(warningPacket);
        }

        public IService Configure(params object[] configuration)
        {
            this.Enabled = true;
            return this;
        }

        public bool Enabled
        {
            get;
            set;
        }

        public Node Client
        {
            get;
            private set;
        }

        private void Handle(BindingUpdatePacket packet)
        {
            if (packet.Lifetime != 0)
            {
                bindingCache.Add(new BindingCache.Entry(packet.HomeAddress, packet.CareOfAddress, packet.Lifetime + Simulator.Instance.Clock));
            }
            else
            {
                bindingCache.Remove(packet.HomeAddress);
            }
        }


        private void Handle(BindingWarningPacket packet)
        {
            // This SHOULD send a request, but it just clears the cache.
            if (bindingCache.Contains(packet.HomeAddress))
                bindingCache.Remove(packet.HomeAddress);
        }


        /// <summary>
        /// Bypass the normal send to encapsulate any packet targeted
        /// at a node in the binding cache
        /// </summary>
        /// <param name="source">The node that generated the event</param>
        /// <param name="eventArgs">Arguments to the event</param>
        private void Client_BeforePacketSent(Node source, Migrator.Events.BeforePacketSentEventArgs eventArgs)
        {
            var packet = eventArgs.Packet;

            // If it's in cache...
            if (bindingCache.Contains(packet.DestinationIP))
            {
                // Encapsulate it!
                var entry = this.bindingCache[packet.DestinationIP];

                IPPacket outter = new IPPacket(this.Client, entry.CareOfAddress);
                outter.Payload = packet;
                
                // And send it.
                this.Client.Send(outter);

                // Cancel the event, so the original packet won't be sent
                eventArgs.Cancel = true;
            }
        }

        /// <summary>
        /// Mobility binding cache
        /// </summary>
        private BindingCache bindingCache = new BindingCache();
    }
}
